package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class IndexService {
    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private DistributedLock distributedLock;
    @Autowired
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:cates:";

    private static final String LOCK_PREFIX = "index:cates:lock:";


    public List<CategoryEntity> queryLvl1Categories() {
        ResponseVo<List<CategoryEntity>> listResponseVo = this.pmsClient.queryCategoriesByPid(0l);
        return listResponseVo.getData();
    }


    @GmallCache(prefix = KEY_PREFIX, timeout = 129600, random = 14400, lock = LOCK_PREFIX)
    public List<CategoryEntity> queryLvl12CategoriesByPid(Long pid) {
        ResponseVo<List<CategoryEntity>> listResponseVo = this.pmsClient.queryCategoriesWithSubsByPid(pid);
        List<CategoryEntity> categoryEntities = listResponseVo.getData();
        return categoryEntities;
    }



    public List<CategoryEntity> queryLvl12CategoriesByPid2(Long pid) {
        //1.先查询缓存，缓存命中直接返回
        String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)){
            //手动序列化
            return JSON.parseArray(json,CategoryEntity.class);
        }

        //为了防止缓存击穿，添加分布式锁
        RLock fairLock = this.redissonClient.getFairLock(LOCK_PREFIX + pid);
        fairLock.lock();


        try {
            // 获取分布式锁的过程中，可能有其他请求已经把数据放入缓存，此时再次查询缓存
            String json2 = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)){
                //手动序列化
                return JSON.parseArray(json2,CategoryEntity.class);
            }

            //2.如果缓存没有命中，则发送远程请求，查询数据库，并放入缓存
            ResponseVo<List<CategoryEntity>> listResponseVo = this.pmsClient.queryCategoriesWithSubsByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();

            //缓存数据到redis
            if (CollectionUtils.isEmpty(categoryEntities)){
                //为了防止缓存穿透，即使数据为空也缓存，但是缓存时间一般不超过5分钟
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid,JSON.toJSONString(categoryEntities),5, TimeUnit.MINUTES);
            }else {
                //为了防止缓存雪崩，给过期时间添加随机值
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid,JSON.toJSONString(categoryEntities),2160+new Random().nextInt(20), TimeUnit.HOURS);
            }

            return categoryEntities;
        } finally {
            fairLock.unlock();
        }
    }

    public  void testLock() {
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();
        try {
            String json = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(json)){
                this.redisTemplate.opsForValue().set("num","1");
            }
            int num = Integer.parseInt(json);
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
            this.testSubLock();
        } finally {
            lock.unlock();
        }
    }


    public void testSubLock() {
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();

        System.out.println("sdfdsfsdfdsfds");

        lock.unlock();
    }




    public  void testLock3() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.distributedLock.tryLock("lock", uuid, 30);
        if (lock){
            String json = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(json)){
                this.redisTemplate.opsForValue().set("num","1");
            }
            int num = Integer.parseInt(json);
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));

//            this.testSubLock(uuid);
            try {
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.distributedLock.unlock("lock",uuid);
        }
    }

    public void testSubLock(String uuid){
        this.distributedLock.tryLock("lock",uuid,30);

        System.out.println("测试可重入锁");

        this.distributedLock.unlock("lock",uuid);
    }

    //synchronized
    public  void testLock2() {
        //1.加锁
        String uuid = UUID.randomUUID().toString();
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
        if (!flag){
            //3.如果没有抢到锁，重试
            try {
                Thread.sleep(80);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            // 查询redis中的num值
            String json = this.redisTemplate.opsForValue().get("num");
            // 没有该值return
            if (StringUtils.isBlank(json)){
                this.redisTemplate.opsForValue().set("num","1");
            }
            // 有值就转成成int
            int num = Integer.parseInt(json);
            // 把redis中的num值+1
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));

            //2.解锁:防止误删，先判断是否自己的锁，如果是自己的锁才能释放
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList("lock"),uuid);


//            if (StringUtils.equals(uuid,this.redisTemplate.opsForValue().get("lock"))){
//                this.redisTemplate.delete("lock");
//            }
        }
    }

    public void testRead() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.readLock().lock(10,TimeUnit.SECONDS);

        //TODO:业务代码
    }

    public void testWrite() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.writeLock().lock(10,TimeUnit.SECONDS);

        //TODO:业务代码
    }

    public void testLatch() throws InterruptedException {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        cdl.trySetCount(6);

        cdl.await();

        //TODO

    }

    public void testDown() {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        cdl.countDown();

    }
}
